import Glibc

class Timer {
    private let CLOCK_REALTIME = 0
    private var start_timespec = timespec()
    private var end_timespec = timespec()
    private var time_spec = timespec()
    func start() {
        clock_gettime(Int32(CLOCK_REALTIME),&start_timespec)
    }
    
    func stop() -> Double {
        clock_gettime(Int32(CLOCK_REALTIME),&end_timespec)
        let start_time = Double(start_timespec.tv_sec * 1_000_000 + start_timespec.tv_nsec / 1_000)
        let end_time = Double(end_timespec.tv_sec * 1_000_000 + end_timespec.tv_nsec / 1_000)
        let time = end_time - start_time
        return time / 1_000
    }
    func getTime() -> Double {
        clock_gettime(Int32(CLOCK_REALTIME),&time_spec)
        return Double(time_spec.tv_sec * 1_000_000 + time_spec.tv_nsec / 1_000)
    }
}

let timer = Timer()

func approximate(_ u:inout[Double], _ v:inout[Double], _ b:inout[Double]) -> Double {
    let n = v.count
    for _ in 1...10 {
        multiplyAtAv(&u,&v,&b)
        multiplyAtAv(&v,&u,&b)
    }
    var vBv = 0.0, vv = 0.0
    for i in 0..<n {
        vBv += u[i]*v[i]
        vv  += v[i]*v[i]
    }
    return sqrt(vBv/vv)
}

func a(_ i: Int, _ j: Int) -> Double {
    let ij = i+j
    return 1.0 / Double( ij*(ij+1)/2 + i+1 )
}

func multiplyAv(_ v:inout[Double], _ av: inout [Double]) {
    let n = v.count
    for i in 0..<n {
        av[i] = 0.0;
        for j in 0..<n {
            av[i] += a(i,j)*v[j]
        }
    }
}

func multiplyAtv(_ v: inout[Double], _ atv: inout [Double]) {
    let n = v.count
    for i in 0..<n {
        atv[i] = 0;
        for j in 0..<n {
            atv[i] += a(j,i)*v[j]
        }
    }
}

func multiplyAtAv(_ v: inout [Double], _ atAv: inout [Double], _ u: inout[Double]) {
    multiplyAv(&v,&u)
    multiplyAtv(&u,&atAv)
}

func runSpectralnorm()->Int{
    let n: Int = 500;
    var u = Array(repeating: 1.0, count: n)
    var v = Array(repeating: 0.0, count: n)
    var b = Array(repeating: 0.0, count: n)
    timer.start()
    _ = approximate(&u, &v, &b)
    let time = timer.stop()
    print("Array Access - RunSpectralNorm:\t"+String(time)+"\tms");
	return Int(time) 
}
_ = runSpectralnorm()